

using System.Diagnostics;

namespace SharpTalk.Core.Data.SqLiteDataProvider
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using Diagnostic;
    using System.Data;
    using Enums;

    /// <summary>
    /// 
    /// </summary>
    public sealed class SqLiteUserDataProvider
        : SqLiteDataProviderBase<User>, IUserDataProvider
    {
        
        /// <summary>
        /// Creates a new instance
        /// </summary>
        /// <param name="filepath"></param>
        public SqLiteUserDataProvider(string filepath)
            : base(filepath)
        {
        }
        /// <summary>
        /// Creates a new instance
        /// </summary>
        /// <param name="filepath"></param>
        /// <param name="traceProvider"></param>
        public SqLiteUserDataProvider(string filepath, ITraceProvider traceProvider) 
            : base(filepath, traceProvider)
        {
        }

        /// <summary>
        /// Deletes all user data
        /// </summary>
        public override void ClearAll()
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            this.ThrowIfDisposed();

            using (var trans = Connection.BeginTransaction(IsolationLevel.Serializable))
                try
                {
                    using (var cmd = Connection.CreateCommand())
                    {
                        cmd.Transaction = trans;
                        cmd.CommandText = CMD_USERS_DELETE_ALL;
                        cmd.ExecuteNonQuery();

                        cmd.CommandText = CMD_TRACKINGS_DELETE_ALL;
                        cmd.ExecuteNonQuery();

                        trans.Commit();
                    }
                }
                catch (Exception exp)
                {
                    trans.Rollback();
                    TraceException(MethodBase.GetCurrentMethod(), exp, true);
                }
            
        }
        /// <summary/>
        /// <param name="obj"/>
        /// <returns/>
        public override bool Save(User obj)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            this.ThrowIfDisposed();

            var success = false;
            try
            {
                using(var cmd = Connection.CreateCommand())
                {
                    cmd.CommandText = CMD_USERS_CREATE;
                    cmd.Parameters.AddWithValue(null,obj.Guid);
                    cmd.Parameters.AddWithValue(null, obj.FirstName);
                    cmd.Parameters.AddWithValue(null, obj.LastName);
                    cmd.Parameters.AddWithValue(null, obj.NickName);
                    cmd.Parameters.AddWithValue(null, obj.Image);
                    cmd.Parameters.AddWithValue(null, (byte)obj.Status);
                    cmd.Parameters.AddWithValue(null, obj.Uri);
                    cmd.Parameters.AddWithValue(null, DateTime.Now);
                        
                    cmd.ExecuteNonQuery();
                }
                success = true;
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
            }
            return success;
        }
        /// <summary/>
        /// <returns/>
        public override List<User> All()
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            this.ThrowIfDisposed();

            var users = new List<User>();
            try
            {
                using (var cmd = Connection.CreateCommand())
                {
                    cmd.CommandText = CMD_USERS_READ_ALL;
                    using (var reader = cmd.ExecuteReader())
                    {
                        if (reader.HasRows)
                            while (reader.Read())
                            {
                                try
                                {
                                    users.Add(new User()
                                                   {
                                                       Guid = reader.GetGuid(0),
                                                       FirstName = reader.IsDBNull(1) ? null : reader.GetString(1),
                                                       LastName = reader.IsDBNull(2) ? null : reader.GetString(2),
                                                       NickName = reader.IsDBNull(3) ? null : reader.GetString(3),
                                                       Image = reader.IsDBNull(4) ? null : reader.GetValue(4) as byte[],
                                                       Status = (UserStatusEnum)reader.GetByte(5),
                                                       Uri = reader.GetString(6)
                                                   });
                                }
                                catch (Exception)
                                {
                                    continue;
                                }
                            }
                    }
                }
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp, true);
            }
            return users;

        }
        /// <summary/>
        /// <param name="obj"/>
        /// <returns/>
        public override User Update(User obj)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            this.ThrowIfDisposed();

            try
            {
                using(var cmd = Connection.CreateCommand())
                {
                    cmd.CommandText = CMD_USERS_UPDATE;
                    cmd.Parameters.AddWithValue(null, obj.FirstName);
                    cmd.Parameters.AddWithValue(null, obj.LastName);
                    cmd.Parameters.AddWithValue(null, obj.NickName);
                    cmd.Parameters.AddWithValue(null, obj.Image);
                    cmd.Parameters.AddWithValue(null, (byte)obj.Status);
                    cmd.Parameters.AddWithValue(null, obj.Uri);
                    cmd.Parameters.AddWithValue(null, DateTime.Now);
                    cmd.Parameters.AddWithValue(null, obj.Guid);

                    cmd.ExecuteNonQuery();
                }
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                return null;
            }

            if(obj.Status != UserStatusEnum.Online)
                try
                {
                    // Delete from ChatRoomUsers
                    using (var cmd = Connection.CreateCommand())
                    {
                        cmd.CommandText = CMD_CHATROOMUSERS_DELETE_USER;
                        cmd.Parameters.AddWithValue(null, obj.Guid);
                        cmd.ExecuteNonQuery();
                    }
                }
                catch (Exception exp)
                {
                    TraceException(MethodBase.GetCurrentMethod(), exp, true);
                }
            
            return obj;
        }
        /// <summary/>
        /// <param name="obj">The User to Delete</param>
        /// <returns/>
        public override bool Delete(User obj)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            this.ThrowIfDisposed();
            return obj != null ? Delete(obj.Guid) : false;
        }
        /// <summary/>
        /// <param name="guid">User Guid</param>
        /// <returns/>
        public override bool Delete(Guid guid)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            this.ThrowIfDisposed();
            
            var success = false;

            using(var trans = Connection.BeginTransaction(IsolationLevel.Serializable))
                try
                {
                    using(var cmd  = Connection.CreateCommand())
                    {
                        cmd.Parameters.AddWithValue(null, guid);

                        // Delete User
                        cmd.CommandText = CMD_USERS_DELETE;
                        cmd.ExecuteNonQuery();

                        // Delete Tracked
                        cmd.CommandText = CMD_TRACKINGS_DELETE_TRACKED;
                        cmd.ExecuteNonQuery();

                        // Delete Tracker
                        cmd.CommandText = CMD_TRACKINGS_DELETE_TRACKER;
                        cmd.ExecuteNonQuery();

                        //  Delete user's chatroom(s) user(s)
                        cmd.CommandText = CMD_CHATROOMUSERS_DELETE_OWNER;
                        cmd.ExecuteNonQuery();

                        // Delete user's chatroom(s)
                        cmd.CommandText = CMD_CHATROOMS_DELETE_OWNER;
                        cmd.ExecuteNonQuery();
                        
                        // Delete user from other users chatrooms 
                        cmd.CommandText = CMD_CHATROOMUSERS_DELETE_USER;
                        cmd.ExecuteNonQuery();

                        trans.Commit();
                    }
                    success = true;
                }
                catch (Exception exp)
                {
                    trans.Rollback();
                    TraceException(MethodBase.GetCurrentMethod(), exp, true);
                }

            return success;
        }
        /// <summary/>
        /// <param name="guid"/>
        /// <returns/>
        public override User Find(Guid guid)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            this.ThrowIfDisposed();

            try
            {
                using (var cmd = Connection.CreateCommand())
                {
                    cmd.CommandText = CMD_USERS_READ_GUID;
                    cmd.Parameters.AddWithValue(null, guid);

                    using (var reader = cmd.ExecuteReader())
                    {
                        if (!reader.HasRows)
                            return null;

                        reader.Read();
                        var user = new User()
                                       {
                                           Guid = reader.GetGuid(0),
                                           FirstName = reader.IsDBNull(1) ? null : reader.GetString(1),
                                           LastName = reader.IsDBNull(2) ? null : reader.GetString(2),
                                           NickName = reader.IsDBNull(3) ? null : reader.GetString(3),
                                           Image = reader.IsDBNull(4) ? null : reader.GetValue(4) as byte[],
                                           Status = (UserStatusEnum)reader.GetByte(5),
                                           Uri = reader.GetString(6)
                                       };
                        return user;
                    }
                }
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                return null;
            }

        }

        public void TrackUserChanges(Guid tracked, Guid tracker)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            this.ThrowIfDisposed();

            try
            {
                using(var cmd = Connection.CreateCommand())
                {
                    cmd.CommandText = CMD_TRACKINGS_CREATE;
                    cmd.Parameters.AddWithValue(null, tracked);
                    cmd.Parameters.AddWithValue(null, tracker);
                    cmd.ExecuteNonQuery();
                }
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
            }
        }

        public void TrackUserChanges(User tracked, User tracker)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            this.ThrowIfDisposed();

            if(tracked !=null && tracker !=null)
                TrackUserChanges(tracked.Guid, tracker.Guid);
        }

        public void TrackUserChanges(Guid tracked, IEnumerable<Guid> trackers)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            this.ThrowIfDisposed();

            if(tracked == Guid.Empty) return;

            foreach (var tracker in trackers.Where(t => t != Guid.Empty))
                TrackUserChanges(tracked, tracker);
        }

        public void TrackUserChanges(User tracked, IEnumerable<User> trackers)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            this.ThrowIfDisposed();

            if (tracked == null || tracked.Guid == Guid.Empty) return;

            foreach (var tracker in trackers.Where(user => user !=null && user.Guid != Guid.Empty ))
                TrackUserChanges(tracked.Guid,tracker.Guid);
        }

        public void TrackUserChanges(IEnumerable<Guid> tracked, Guid tracker)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            this.ThrowIfDisposed();

            foreach (var user in tracked.Where(guid => guid != Guid.Empty ))
                TrackUserChanges(user,tracker);
        }

        public void TrackUserChanges(IEnumerable<User> tracked, User tracker)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            this.ThrowIfDisposed();

            if(tracker == null || tracker.Guid == Guid.Empty )return;

            foreach (var user in tracked.Where(t => t !=null && t.Guid != Guid.Empty ))
                TrackUserChanges(user.Guid, tracker.Guid);
        }

        public List<User> GetUserTrackingList(Guid tracked)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            this.ThrowIfDisposed();

            var trackers = new List<User>();
            try
            {
                using(var cmd = Connection.CreateCommand())
                {
                    cmd.CommandText = CMD_TRACKINGS_READ_TRACKED;
                    cmd.Parameters.AddWithValue(null, tracked);
                    using(var reader = cmd.ExecuteReader())
                    {
                        if(reader.HasRows)
                            while(reader.Read())
                            {
                                trackers.Add(new User()
                                                 {
                                                     Guid = reader.GetGuid(0),
                                                     FirstName = reader.IsDBNull(1) ? null : reader.GetString(1),
                                                     LastName = reader.IsDBNull(2) ? null : reader.GetString(2),
                                                     NickName = reader.IsDBNull(3) ? null : reader.GetString(3),
                                                     Image = reader.IsDBNull(4) ? null : reader.GetValue(4) as byte[],
                                                     Status = (UserStatusEnum) reader.GetByte(5),
                                                     Uri = reader.GetString(6)
                                                 });
                            }
                    }
                }
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
            }
            return trackers;
        }

        public List<User> GetUserTrackingList(User tracked)
        {
            if (ShouldTrace(TraceEventType.Verbose))TraceMethodCall(MethodBase.GetCurrentMethod());
            this.ThrowIfDisposed();

            return GetUserTrackingList(tracked.Guid);
        }
    }
}